//#preprocess
/**
 * AUTO_COPYRIGHT_SUB_TAG
 */
package com.rim.samples.device.push.lib;

//#ifdef HANDHELD_VERSION_50
import java.util.*;

import javax.microedition.io.*;

import log.Logger;

import net.rim.blackberry.api.push.*;
import net.rim.device.api.io.http.*;
import net.rim.device.api.system.*;
import net.rim.device.api.ui.*;

import com.rim.samples.device.push.*;
import com.rim.samples.device.push.cmd.NetworkCommand.Transaction;

/**
 * Library for 5.0+ devices.
 * <p>
 * This library uses new Push API and has the following advantages over 4.2 devices.
 * <ul>
 * <li>Application doesn't have to be running in the background waiting for push messages</li>
 * <li>Push API performs automatic registration with BPS server</li>
 * <li>API automatically notifies application on SIM swap event</li>
 * <li>BPS server is notified when user un-installs the application</li>
 * </ul>
 */
public class PushLib50 extends UiApplication implements PushApplication, BpasProtocol, PushMessageListener {

    private MessageReadingThread thread;

    /**
     * Implementation of PushApplication interface. This method is called when an incoming push message is received. The method is
     * called on the application event thread.
     */
    public void onMessage( PushInputStream inputStream, StreamConnection conn ) {
        // process messages asynchronously because this is event thread of an UI application
        MessageReadingThread reader = thread;
        if( reader != null ) {
            reader.enqueue( inputStream, conn );
        } else {
            // not listening, decline and close the connection
            if( PushConfig.isApplicationAcknoledgementSupported() ) {
                PushUtils.decline( inputStream, PushInputStream.DECLINE_REASON_USERPND );
            }
            PushUtils.close( conn, inputStream, null );
        }
    }

    /**
     * Implementation of PushApplication interface. This method is called when BPS registration status changes.
     */
    public void onStatusChange( PushApplicationStatus pushAppStatus ) {
        byte bpsStatus = pushAppStatus.getStatus();
        byte regReason = pushAppStatus.getReason();
        String error = pushAppStatus.getError();

        boolean simChanged = false;

        String logStatus = "Unknown " + bpsStatus;

        switch( bpsStatus ) {
            case PushApplicationStatus.STATUS_ACTIVE:
                logStatus = "Active";
                break;
            case PushApplicationStatus.STATUS_FAILED:
                logStatus = "Failed";
                break;
            case PushApplicationStatus.STATUS_NOT_REGISTERED:
                logStatus = "Not Registered";
                switch( pushAppStatus.getReason() ) {
                    case PushApplicationStatus.REASON_SIM_CHANGE:
                        simChanged = true;
                        break;
                    case PushApplicationStatus.REASON_API_CALL:
                        // should not happen, even if called then we already unregistered
                        break;
                    case PushApplicationStatus.REASON_REJECTED_BY_SERVER:
                    case PushApplicationStatus.REASON_NETWORK_ERROR:
                    case PushApplicationStatus.REASON_INVALID_PARAMETERS:
                        // registration failed
                        break;
                }
                break;
            case PushApplicationStatus.STATUS_PENDING:
                logStatus = "Pending";
                break;
        }

        if( error != null ) {
            Logger.log( "Push API status callback: " + logStatus + ", error " + error );
        } else {
            Logger.log( "Push API status callback: " + logStatus );
        }
        
        setResultAndResumeTransaction( bpsStatus, regReason, error );

        if( simChanged ) {
            PushController.onSimChange();
        }
    }

    // ****************** BpasProtocol implementation ************************

    // keep transaction for registration
    private Object txLock = new Object();
    private Transaction current;
    private byte currentRegistration;
    private byte currentReason;
    private String currentError;
    
    public void register( Transaction tx, boolean isEnterprise ) throws Exception {
        int port = PushConfig.getPort();
        String appId = PushConfig.getAppId();
        String bpsUrl = PushConfig.getBpsUrl();
        ApplicationDescriptor ad = ApplicationDescriptor.currentApplicationDescriptor();
        
        // server type depends whether we get pushes through BES or BIS
        byte serverType = isEnterprise ? PushApplicationDescriptor.SERVER_TYPE_NONE : PushApplicationDescriptor.SERVER_TYPE_BPAS;
        // if enterprise network then there is no server URL
        bpsUrl = isEnterprise ? null : bpsUrl;

        PushApplicationDescriptor pad = new PushApplicationDescriptor( appId, port, bpsUrl, serverType, ad );

        // check whether already registered or registration pending
        PushApplicationStatus pushApplicationStatus = PushApplicationRegistry.getStatus( pad );
        byte pasStatus = pushApplicationStatus.getStatus();
        if( pasStatus == PushApplicationStatus.STATUS_ACTIVE ) {
            // we already registered, update the statuses
            Logger.log( "Already registered with Push API" );
            return;
            
        } else if( pasStatus == PushApplicationStatus.STATUS_PENDING ) {
            // we already scheduled registration, wait for its result
            Logger.log( "Registration with Push API already scheduled");
            setCurrentTransaction( tx );
            tx.waitForNotification();
            
        } else {
            // not registered yet, register
            Logger.log( "Scheduled registration with Push API");
            PushApplicationRegistry.registerApplication( pad );
            setCurrentTransaction( tx );
            tx.waitForNotification();
        }
        readRegistrationResult();
    }

    public void unregister( Transaction tx, boolean isEnterprise ) {
        // Push API unregisters the application right away,
        // so we don't wait for notifications
        PushApplicationRegistry.unregisterApplication();
        Logger.log( "Un-registred from Push API" );
    }
    
    private void setCurrentTransaction( Transaction tx ) {
        synchronized( txLock ) {
            if( current != null && current != tx ) {
                current.cancelAndNotify();
            }
            current = tx;
            currentRegistration = -1;
            currentReason = -1;
            currentError = null;
        }
    }
    
    private void setResultAndResumeTransaction( byte registration, byte reason, String error ) {
        synchronized( txLock ) {
            currentRegistration = registration;
            currentError = error;
            currentReason = reason;
            if( current != null ) {
                current.resumeAndNotify();
                current = null;
            }
        }
    }
    
    private void readRegistrationResult() throws Exception {
        synchronized( txLock ) {
            if( currentRegistration != -1 ) {
                switch( currentRegistration ) {
                    case PushApplicationStatus.STATUS_ACTIVE:
                        // successful registration
                        return;
                    case PushApplicationStatus.STATUS_FAILED:
                    case PushApplicationStatus.STATUS_NOT_REGISTERED:
                    case PushApplicationStatus.STATUS_PENDING:
                        if( currentError != null ) {
                            throw new Exception( "Registration with Push API failed, caused by " + currentError );
                        } else {
                            String error;
                            switch( currentReason ) {
                                case PushApplicationStatus.REASON_INVALID_PARAMETERS:
                                    error = "Registration with Push API failed due to invalid parameters";
                                    break;
                                case PushApplicationStatus.REASON_NETWORK_ERROR:
                                    error = "Registration with Push API failed due to network problems";
                                    break;
                                case PushApplicationStatus.REASON_REJECTED_BY_SERVER:
                                    error = "Registration with Push API was rejected by server";
                                    break;
                                default:
                                    error = "Registration with Push API failed";
                                    break;
                            }
                            throw new Exception( error );
                        }
    
                }
                currentRegistration = -1;
                currentReason = -1;
                currentError = null;
            }
        }
    }

    // **************** PushMessageListener implementation ********************

    public void startListening() {
        // start listening thread
        if( thread == null ) {
            thread = new MessageReadingThread();
            thread.start();
        }
    }

    public void stopListening() {
        if( thread != null ) {
            thread.stopRunning();
            thread = null;
        }
    }

    public boolean applicationCanQuit() {
        // 5.0+ API will start our application when something interesting happens
        // this means that the listener doesn't have to be constantly running in the background
        return true;
    }

    /**
     * Thread that processes incoming connections through {@link PushMessageReader}
     */
    private static class MessageReadingThread extends Thread {

        private Stack queue;
        private boolean running;

        public MessageReadingThread() {
            this.queue = new Stack();
            this.running = true;
        }

        public void enqueue( PushInputStream inputStream, StreamConnection conn ) {
            synchronized( queue ) {
                queue.insertElementAt( inputStream, 0 );
                queue.insertElementAt( conn, 0 );
                queue.notifyAll();
            }
        }

        public void run() {
            Logger.log( "Listening for push messages through PushAPI" );
            try {
                PushInputStream inputStream;
                StreamConnection conn;
                while( running ) {
                    inputStream = null;
                    conn = null;
                    synchronized( queue ) {
                        if( queue.isEmpty() ) {
                            queue.wait();
                        } else {
                            inputStream = (PushInputStream) queue.pop();
                            conn = (StreamConnection) queue.pop();
                        }
                    }
                    if( inputStream != null ) {
                        PushMessageReader.process( inputStream, conn );
                    }
                }
            } catch( InterruptedException e ) {
            }
            Logger.log( "Stopped listening for push messages" );
        }

        public void stopRunning() {
            running = false;
            synchronized( queue ) {
                queue.notifyAll();
            }
        }

    }
}
/*
//#endif
//#ifndef HANDHELD_VERSION_50
/*
public class PushLib50 {
}
//#endif
*/